//
//  ReplayKitExample+Capture.swift
//  SwiftCode
//
//  Created by yuhus on 2023/9/21.
//  主要用于录制 App 内容，无法过滤滤系统通知和键盘等 UIView。需要处理 RPSampleBufferType 数据

import Foundation
import ReplayKit

extension ReplayKitExample {
  func initCapture() {
    guard let outputURL = getFilePathURL() else {
      Timber.d("Initialization failed which the location of the file to write")
      return
    }
    assetWriter = try? AVAssetWriter.init(url: outputURL, fileType: .mp4)
    guard let assetWriter else {
      Timber.d("Writing fails if a file already exists at the output URL.")
      return
    }
    
    let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: nil)
    videoInput.expectsMediaDataInRealTime = true
    
    guard assetWriter.canAdd(videoInput) else {
      // TODO: - 添加失败
      /*
       Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[AVAssetWriter addInput:] In order to perform passthrough to file type public.mpeg-4, please provide a format hint in the AVAssetWriterInput initializer'
       terminating with uncaught exception of type NSException
       */
      return
    }
    assetWriter.add(videoInput)
    self.videoInput = videoInput
  }
  
  @objc func captureButtonTapped() {
    guard assetWriter != nil else {
      Timber.d("Initialization failed!")
      return
    }
    if isActive == false {
      startCapture()
    } else {
      stopCapture()
    }
  }
  
  private func startCapture() {
    guard RPScreenRecorder.shared().isAvailable else { return }
    
    RPScreenRecorder.shared().startCapture { sampleBuffer, bufferType, error in
      if let error {
        Timber.d(error)
        return
      }
      switch bufferType {
      case .video:
        self.processAppVideoSample(sampleBuffer)
      case .audioApp:
        self.processAppAudioSample(sampleBuffer: sampleBuffer)
      case .audioMic:
        self.processAppMicSample(sampleBuffer: sampleBuffer)
      default:
        debugPrint("unkown")
      }
    } completionHandler: { error in
      if let error {
        Timber.d(error)
        return
      }
      self.setCaptureState(active: true)
    }
  }
  
  private func processAppVideoSample(_ sbuf: CMSampleBuffer) {
    // An app can modify the video sample buffers as necessary.
    // The sample simply prints a message to the console.
//    Timber.d("Received a video sample.")
    guard let assetWriter, let videoInput, CMSampleBufferDataIsReady(sbuf) else { return }
    
    guard assetWriter.status == .writing else { return }
    switch assetWriter.status {
    case .unknown:
      assetWriter.startWriting()
      let time = CMSampleBufferGetPresentationTimeStamp(sbuf)
      assetWriter.startSession(atSourceTime: time)
    case .writing:
      if videoInput.isReadyForMoreMediaData {
        videoInput.append(sbuf)
        Timber.d("append smaple:", sbuf)
      }
    case .failed, .cancelled, .completed:
      return
    @unknown default:
      fatalError("@unknown")
    }
  }
  
  private func processAppAudioSample(sampleBuffer: CMSampleBuffer) {
    // An app can modify the audio sample buffers as necessary.
    // The sample simply prints a message to the console.
    Timber.d("Received an audio sample.")
  }
  
  private func processAppMicSample(sampleBuffer: CMSampleBuffer) {
    // An app can modify the microphone audio sample buffers as necessary.
    // The sample simply prints a message to the console.
    Timber.d("Received a microphone audio sample.")
  }
  
  private func stopCapture() {
    RPScreenRecorder.shared().stopCapture { error in
      // The sample calls the handler when the stop capture finishes. Update the capture state.
      self.setCaptureState(active: false)
      
      // Check and print the error, if necessary.
      if error != nil {
        print("Encountered and error attempting to stop in app capture")
      }
    }
  }
  
  func setCaptureState(active: Bool) {
    DispatchQueue.main.async {
      let title = active == true ? "Stop Capture" : "Start Capture"
      self.captureButton.setTitle(title, for: .normal)
      
      // Set the internal recording state.
      self.isActive = active
    }
  }
}
